import datetime
import pymongo
import Core.Gadget as Gadget
import Core.Algorithm as Algo
import Core.IO as IO
import Core.DataSeries as DataSeries
import math
import copy
from Factors.General import *
from sklearn import linear_model

def MomentumCallback(dfData, factorName, params):
    #
    length = params["Period"]
    # fieldName = params["Field"]
    dfData[factorName] = dfData["BClose"] / dfData["BClose"].shift(length) - 1


def BiasCallback(dfData, factorName, params):
    #
    length = params["Period"]
    # fieldName = params["Field"]
    dfData["Mean"] = dfData["BClose"].rolling(window=length).mean()
    dfData[factorName] = dfData["BClose"] / dfData["Mean"] - 1


def ReturnsCallback(dfData, factorName, params):

    if factorName == "DailyReturn":
        dfData[factorName] = dfData["BClose"] / dfData["BClose"].shift(1) - 1
    elif factorName == "DailyLogReturn":
        dfData[factorName] = np.log(dfData["BClose"] / dfData["BClose"].shift(1))


def CalcReturn(database, instruments, datetime1, datetime2, update=False):
    count = 0
    #for instrument in instruments:
    #    count = count + 1
    #    print(instrument["Symbol"] + " " + str(count))
    #    if instrument["Symbol"] == "000555.SZ":
    #        kkwood = 0

    CalcDailyFactor(database, instruments, datetime1, datetime2, "DailyReturn", ReturnsCallback, update,
                    startIndex=0)
    CalcDailyFactor(database, instruments, datetime1, datetime2, "DailyLogReturn", ReturnsCallback, update)


def CalcMonthlyReturn(database, instruments, datetime1, datetime2, update=False):
    #
    print("Calc Monthly Return")

    # ---Build cache, use month date to find "Trading date"---
    bmInstrument = database.Find("Instruments","Index", {"Symbol":"000001.SH"})
    bmInstrument = bmInstrument[0]
    dfBenchmark = IO.LoadBarsAsDataFrame(database, bmInstrument["Symbol"], instrumentType="Index",
                                         datetime1=datetime1, datetime2=datetime2, fields=["Close"])
    #
    # latestTradeDate = dfBenchmark.iloc[-1, :]["StdDateTime"]
    # print(dfBenchmark.head())

    #
    factorName = "MonthlyReturn"
    count = 0
    for instrument in instruments:
        count += 1
        symbol = instrument["Symbol"]

        print("Monthly Return " + symbol + " Count " + str(count))
        # ---构建容器---
        returnsDataSeries = DataSeries.DataSeries(symbol + "_" + factorName + "_Factor")
        excessReturnDataSeries = DataSeries.DataSeries(symbol + "_MonthlyExcessReturn_Factor")

        # ---确定起始日期---
        realDateTime1, realDateTime2 = DeterminFactorCalculatingRange(database, instrument, datetime1, datetime2,
                                                                      factorName, update)

        referenceDateTime1 = realDateTime1 + datetime.timedelta(days=-1 * 32)


        #
        instrumentType = instrument["InstrumentType"]
        if instrumentType == "Stock":
            dfBars = IO.LoadBarsAsDataFrame(database, symbol, instrumentType=instrumentType,
                                            datetime1=referenceDateTime1, datetime2=realDateTime2,
                                            fields=["BClose","TradeStatus"])

        # Might be Index
        elif instrumentType == "Index":
            dfBars = IO.LoadBarsAsDataFrame(database, symbol, instrumentType=instrumentType,
                                            datetime1=referenceDateTime1, datetime2=realDateTime2,
                                            fields=["Close"])
            dfBars.rename(columns={'Close': "BClose"}, inplace=True)

        # neither Stock or Index
        if len(dfBars) == 0:
            print(symbol + " Missing Total Data " + symbol)
            continue

        # 已经退市企业，再算最后一个月Return
        if instrument["DateTime2"] < Gadget.MaxDateTime():
            realDateTime2 = realDateTime2 + datetime.timedelta(days=1 * 32)
        else: # 在市企业，最后一个bar为截止日期， 最后一个月Return不会被计算
            realDateTime2 = dfBars.iloc[-1, :]["StdDateTime"]

        # print(dfBars.head())
        # dfBars.to_csv("d:/data/monthlyReturn/" + symbol + "_raw.csv")

        dfCombine = pd.merge(dfBenchmark, dfBars, on='StdDateTime', how='left')
        # dfCombine.set_index(['ReportDate'], inplace=True)
        dfCombine.fillna(method='ffill', inplace=True)
        dfCombine.dropna(inplace=True)

        # print(dfCombine.head())
        dfCombine.to_csv("d:/data/monthlyReturn/" + symbol + ".csv")

        # ---first day of month---
        monthDates = Gadget.GenerateMonthDates(referenceDateTime1, realDateTime2)

        output = False
        # ---Loop DateTimes---
        lastRow = pd.DataFrame()
        for lastDayofMonth in monthDates: # 已经是 UTC-Time
            #
            # ---Find Recent Trade Date Value---
            dfTemp = dfCombine[dfCombine['StdDateTime'] <= lastDayofMonth]
            if len(dfTemp) == 0:
                continue

            row = dfTemp.iloc[-1, :]
            # print(dfTemp)
            # print(dfTemp["DateTime"])
            # continue
            tradeDateTime = row["DateTime"]

            #
            if not lastRow.empty:
                #print(" Calc " + str(periodBeginDt) + " to " + str(periodEndDt))
                #
                #if output:
                #    print(lastRow)
                #    print(row)
                returns = row["BClose"] / lastRow["BClose"] - 1
                document = GenarateDocument(symbol, tradeDateTime, returns)

                if "TradeStatus" in dfCombine.columns:
                    document["Entry"] = lastRow["TradeStatus"]
                    document["Exit"] = row["TradeStatus"]
                print(" Calculated Monthly Return ", lastRow["StdDateTime"], " to ", row["StdDateTime"], " Value ", returns)

                #
                # print(symbol + " value " + str(value) + " tradeDateTime " + str(tradeDateTime))
                returnsDataSeries.Add(document)

                #
                bmReturns = row["Close"] / lastRow["Close"] - 1
                excessReturns = returns - bmReturns
                document2 = GenarateDocument(symbol, tradeDateTime, excessReturns)
                excessReturnDataSeries.Add(document2)

            # ---Begin not Null---
            # --- 注意：第一个月的计算结果应该是不准确的---
            lastRow = row

        # ---Loop DataTimes---
        database.saveDataSeries(returnsDataSeries)
        database.saveDataSeries(excessReturnDataSeries)
        pass
    # --- Loop DataSeries ---


def CalcMomentum(database, instruments, datetime1, datetime2, update):
    print("Calc Momentum")
    params = {"Period": 20}
    CalcDailyFactor(database, instruments, datetime1, datetime2, "Momentum_20D", MomentumCallback, update=update,
                    params=params)


def CalcBias(database, instruments, datetime1, datetime2, update):
    params = {"Period": 20}
    CalcDailyFactor(database, instruments, datetime1, datetime2, "Bias_20D", BiasCallback, update=update, params=params)


def CalcPriceFactor(database, instruments, datetime1, datetime2, update=False):
    #
    CalcMomentum(database, instruments, datetime1, datetime2, update)
    CalcBias(database, instruments, datetime1, datetime2, update)


# ---Calc Return---
# ---Based DataSeries---
def CalcReturnOld(database, instruments, datetime1, datetime2, update=False):

    count = 0
    for instrument in instruments:
        count = count + 1
        print(instrument["Symbol"] + " " + str(count))
        if instrument["Symbol"] == "000555.SZ":
            kkwood = 0

    count = 0
    for instrument in instruments:
        #count = count + 1
        #if count > 240:
        #    continue

        #if count <= 23:
        #    continue
        symbol = instrument["Symbol"]

        #dataSeries = database.getDataSeries(symbol + "_Time_86400_Bar", instrumentType="Stock")
        #print(symbol + " " + str(dataSeries.Count()))
        #continue

        # database.creatIndex("Factor",symbol,"Name")
        # database.creatIndex("Factor",symbol,"StdDateTime")
        # database.creatIndex("Factor",symbol,"Params")

        dailyReturns = DataSeries.DataSeries(symbol + "_DailyReturn_Factor")
        dailyLogReturns = DataSeries.DataSeries(symbol + "_DailyLogReturn_Factor")
        barSeries = database.getDataSeries(symbol + "_Time_86400_Bar", instrument["Type"], datetime1, datetime2)

        lastBar = None
        for bar in barSeries:
            if lastBar == None:
                lastBar = bar
                continue

            if lastBar["Values"]["BClose"] == 0:
                ret = math.nan
                logRet = math.nan
            elif bar["Values"]["BClose"] == 0:
                ret = math.nan
                logRet = math.nan
            else:
                ret = bar["Values"]["BClose"] / lastBar["Values"]["BClose"] - 1
                logRet = math.log(bar["Values"]["BClose"] / lastBar["Values"]["BClose"])

            #---
            document = {}
            document["Symbol"] = symbol
            document["StdDateTime"] = bar["StdDateTime"]
            localDateTime = Gadget.ToLocalDateTime(bar["StdDateTime"])
            document["DateTime"] = Gadget.ToDateTimeString(localDateTime)
            #
            doc1 = copy.deepcopy(document)
            doc1["Value"] = ret
            doc1["DataSeries"] = dailyReturns.name
#           #
            doc2 = copy.deepcopy(document)
            doc2["Value"] = logRet
            doc2["DataSeries"] = dailyLogReturns.name
            #
            dailyReturns.Add(doc1)
            dailyLogReturns.Add(doc2)

            lastBar = bar

        # ---Write to Database---
        database.saveDataSeries(dailyReturns)
        database.saveDataSeries(dailyLogReturns)
        print("Calculated Return Factor:" + symbol + " length:" + " count:" + str(count) + " @Time " + str(datetime.datetime.now()))


def CalcMonthlyReturnOld(database, instruments, datetime1, datetime2, update=False):
    #
    print("Calc Monthly Return")

    # ---Build cache, use month date to find "Trading date"---
    tradingDate = {}
    bmBarSeries = database.getDataSeries("000001.SH_Time_86400_Bar", datetime1, datetime2, "Index")
    realDateTime1 = bmBarSeries.First()["StdDateTime"]
    realDateTime2 = bmBarSeries.Last()["StdDateTime"]
    datetimes = Gadget.GenerateMonthDates(realDateTime1, realDateTime2)
    for dt in datetimes:
        quote = bmBarSeries.Get(dt, "Previous")
        tradingDate[dt] = quote["StdDateTime"]

    #
    start = False
    for instrument in instruments:
        symbol = instrument["Symbol"]
        print("Process " + symbol)
        # ---构建容器---
        dataSeries = DataSeries.DataSeries(symbol + "_MonthlyReturn_Factor")
        #
        #
        #if symbol == "603963.SH":
        #    start = True
        #if start == False:
        #    continue
        #
        if update:
            factorInDatabase = database.find("Factor","MonthlyReturn",query={"Symbol":symbol})
            latestDate = factorInDatabase

        #
        quotes = database.getDataSeries(symbol + "_Time_86400_Bar", instrumentType="Stock")
        if quotes.Count() == 0:
            quotes = database.getDataSeries(symbol + "_Time_86400_Bar", instrumentType="Index")
        quotesBeginDt = quotes.First()["StdDateTime"]
        quotesEndDt = quotes.Last()["StdDateTime"]

        # ---Greater than IPO Date, Less than De list Date---
        if datetime1 < quotesBeginDt:
            realDateTime1 = quotesBeginDt
        else:
            realDateTime1 = datetime1
        #
        if datetime2 > quotesEndDt:
            realDateTime2 = quotesEndDt
        else:
            realDateTime2 = datetime2

        # ---first day of month---
        datetimes = Gadget.GenerateMonthDates(realDateTime1, realDateTime2)

        #

        if quotes.Count() == 0:
            print(symbol + " Missing Total Data " + symbol)
            continue

        # ---Loop DateTimes---
        periodBeginDt = None
        for dt in datetimes:
            #
            # end = Gadget.ToUTCDateTime(dt) # 已经是 UTC-Time
            periodEndDt = dt
            #
            if periodBeginDt is not None:
                print(" Calc " + str(periodBeginDt) + " to " + str(periodEndDt))

                #
                first = quotes.Get(periodBeginDt, "Previous")
                last = quotes.Get(periodEndDt, "Previous")
                #
                document = {}
                document["Symbol"] = symbol
                document["StdDateTime"] = tradingDate[dt]
                document["Entry"] = first["TradeStatus"]
                document["Exit"] = last["TradeStatus"]
                print(" Actual " + first["DateTime"] + " to " + last["DateTime"])
                document["Value"] = last["Values"]["BClose"] / first["Values"]["BClose"] - 1

                #
                dataSeries.Add(document)

            # ---Begin not Null---
            # --- 注意：第一个月的计算结果应该是不准确的---
            periodBeginDt = periodEndDt
        # ---Loop DataTimes---
        database.saveDataSeries(dataSeries)
        # database.saveDataSeries(dataSeries, isUpSert=False)
    # --- Loop DataSeries ---


# Old Fashion
def CalcMonthlyReturnOld(database,instruments,datetime1,datetime2):

    datetimes = Gadget.GenerateMonthDates(datetime1, datetime2)
    # for dt in datetimes:
    #     print(dt)
    begin = None
    for dt in datetimes:
        #
        end = Gadget.ToUTCDateTime(dt)
        #
        if begin != None:
            benchmark = database.getDataSeries("000001.SH_Time_86400_Bar", "Index", begin, end)
            count = benchmark.count()
            if count == 0:
                continue

            end = benchmark.getLast()["StdDateTime"]
            print("begin " + str(Gadget.ToLocalDateTime(begin)) + " End " + str(Gadget.ToLocalDateTime(end)))
            #
            for instrument in instruments:
                symbol = instrument["Symbol"]

                if symbol == "600899.SH":
                    kkwood = 0

                if begin < instrument["IPO_Date"]:
                    continue
                if "Delist_Date" in instrument and end > instrument["Delist_Date"]:
                    continue

                quotes = database.getDataSeries(symbol + "_Time_86400_Bar", "Stock", begin, end)
                #
                if quotes.Count() == 0:
                    print(symbol + " Missing Total Data")
                    continue
                #
                localCount = quotes.Count()
                if localCount != count:
                    print(symbol + " Missing Patial Data")
                    continue

                param = {}
                entry = quotes.getFirst()
                exit = quotes.getLast()
                param["Entry"] = entry["TradeStatus"]
                param["Exit"] = exit["TradeStatus"]
                rangeReturn = exit["Values"]["BClose"] / entry["Values"]["BClose"] - 1

                IO.WriteFactorToDataBase(database, symbol, "MonthlyReturn", end, rangeReturn, param)

        begin = end